#include "aceMesh_task.h"
#include "aceMesh_runtime_c.h"

//void initialize(long length, __attribute__((unused)) long chunk_size, double data_A[length], double data_B[length])
void initialize(long length, __attribute__((unused)) long chunk_size, double *data_A, double *data_B)
{
    long i;
    for ( i = 0; i < length; i++) {
        data_A[i] = i + 1;
        data_B[i] = 1;
    }
}

//double dot_product(long N, __attribute__((unused)) long CHUNK_SIZE, double A[N], double B[N])
double dot_product(long N, __attribute__((unused)) long CHUNK_SIZE, double *A, double *B)
{
    double acc;
    long i;

    acc = 0.0;
    for (i = 0; i < N; i++) {
        acc += A[i]*B[i];
    }

    return acc;
}

extern void slave_init_core(targs* args);
extern void slave_dot_core(targs* args);
extern void slave_reduction_core(targs2* args);



void init_inner_core(double *data_A, double *data_B, long startii, long endii)
{
    long ii;
    for(ii=startii;ii<endii;++ii)
    {
        data_A[ii]=ii+1;
        data_B[ii]=1;
    }
}

void init_core(targs* args)
{
    init_inner_core(args->deta_A,args->deta_B,args->startii,args->endii);
}

targs each;
//void initialize_dag(long length, long chunk_size, double data_A[length], double data_B[length])
void initialize_dag(long length, long chunk_size, double *data_A, double *data_B)
{
    long n_chunks;
    long actual_size;
    long i;

    n_chunks = length/chunk_size;
    n_chunks = (n_chunks*chunk_size < length)? n_chunks + 1 : n_chunks;

    acemesh_begin_split_task("init");
    for ( i = 0; i < length; i += chunk_size) {

        actual_size = (length - i >= chunk_size)? chunk_size : (length - i);

        //#pragma oss task out(data_A[i;actual_size]) out(data_B[i;actual_size])
        //{
        
        each.startii=i;
        each.endii=i+actual_size;
        each.deta_A=data_A;
        each.deta_B=data_B;
        acemesh_push_wlist(1, &data_A[i],NORMAL);
        acemesh_push_wlist(1, &data_B[i],NORMAL);
#ifdef MASTER
        acemesh_task_map_master();
        acemesh_task_generator((TASK_FUNCPTR)init_core,(void*)(&each), sizeof(targs));
#else
        acemesh_task_generator((TASK_FUNCPTR)slave_init_core,(void*)(&each), sizeof(targs));
#endif
        acemesh_task_set_type(STENCIL_TASK);
        //    for (long ii = 0; ii < actual_size; ++ii) {
        //        data_A[i + ii] = i + ii + 1;
        //        data_B[i + ii] = 1;
        //    }
        //}
    }
    acemesh_end_split_task();
    acemesh_spawn_and_wait(2);
}




void dot_inner_core(double *acc, double *A, double *B, long startii, long endii)
{
    long ii;
    for (ii = startii; ii < endii; ii++)
    {
        //*acc = *acc + *(A+ii) * *(B+ii);
        *acc += A[ii]*B[ii];
    }
}
void dot_core(targs* args)
{
    dot_inner_core(args->acc, args->deta_A,args->deta_B,args->startii,args->endii);
}



void reduction_inner_core(double *result,double *acc, int length)
{
    long ii;
//    printf("length=%d,%p,%lf,%p\n",length,result,*result,acc);
    //*result=0;
    for ( ii = 0; ii < length; ii++)
    {
       //*result=0;
       *result += acc[ii];
    }
}
void reduction_core(targs2* args)
{
    reduction_inner_core(args->result, args->acc, args->length);
}

double dot_product_dag(long N, long CHUNK_SIZE, double *A, double *B, double* acc)
{
    long N_CHUNKS;
    long actual_size;
    //double acc;
    long i;

    N_CHUNKS = N/CHUNK_SIZE;
    N_CHUNKS = (N_CHUNKS*CHUNK_SIZE < N)? N_CHUNKS + 1 : N_CHUNKS;

    //acc = 0.0;
    //acemesh_begin_split_task("dot");
    for (i = 0; i < N; i += CHUNK_SIZE) {

        actual_size = (N - i >= CHUNK_SIZE)? CHUNK_SIZE : (N - i);
        acemesh_begin_split_task("dot");
        each.startii=i;
        each.endii=i+actual_size;
        each.deta_A=A;
        each.deta_B=B;
        each.acc=acc;
        acemesh_push_rlist(1, &A[i],NORMAL);
        acemesh_push_rlist(1, &B[i],NORMAL);
        acemesh_push_wrlist(1, acc,NORMAL);
#ifdef MASTER
        acemesh_task_map_master();
        acemesh_task_generator((TASK_FUNCPTR)dot_core,(void*)(&each), sizeof(targs));
#else
        acemesh_task_generator((TASK_FUNCPTR)slave_dot_core,(void*)(&each), sizeof(targs));
#endif
        acemesh_task_set_type(STENCIL_TASK);
        acemesh_end_split_task();
        //#pragma oss task in(A[i;actual_size], B[i;actual_size]) inout(acc)
        //{
            //for (long ii = 0; ii < actual_size; ii++)
            //    acc += A[i + ii]*B[i + ii];
        //}
    }
    //acemesh_end_split_task();
    
    //acemesh_spawn_and_wait(2);
    //#pragma oss taskwait
    //return acc;
}

targs2 each2;
double dot_product_dag_reduction(long N, long CHUNK_SIZE, double *A, double *B, double*acc, int length, double *result)
{
    long N_CHUNKS;
    long actual_size;
    long i;
    //double acc[length];
    //double result=0;
  
    //double *acc = (double*)malloc(length*8);
    //memset(acc,0,sizeof(double )*length);    

    N_CHUNKS = N/CHUNK_SIZE;
    N_CHUNKS = (N_CHUNKS*CHUNK_SIZE < N)? N_CHUNKS + 1 : N_CHUNKS;

    //acc = 0.0;
    acemesh_begin_split_task("dot");
    for ( i = 0; i < N; i += CHUNK_SIZE) {

        actual_size = (N - i >= CHUNK_SIZE)? CHUNK_SIZE : (N - i);
        //acemesh_begin_split_task("dot");
        each.startii=i;
        each.endii=i+actual_size;
        each.deta_A=A;
        each.deta_B=B;
        each.acc=&acc[i/CHUNK_SIZE];
        acemesh_push_rlist(1, &A[i],NORMAL);
        acemesh_push_rlist(1, &B[i],NORMAL);
        acemesh_push_wrlist(1, &acc[i/CHUNK_SIZE],NORMAL);
#ifdef MASTER
        acemesh_task_map_master();
        acemesh_task_generator((TASK_FUNCPTR)dot_core,(void*)(&each), sizeof(targs));
#else
        acemesh_task_generator((TASK_FUNCPTR)slave_dot_core,(void*)(&each), sizeof(targs));
#endif
        acemesh_task_set_type(STENCIL_TASK);
        //acemesh_end_split_task();
        //#pragma oss task in(A[i;actual_size], B[i;actual_size]) inout(acc)
        //{
            //for (long ii = 0; ii < actual_size; ii++)
            //    acc += A[i + ii]*B[i + ii];
        //}
    }
    acemesh_end_split_task();
    acemesh_spawn_and_wait(2);

    acemesh_begin_split_task("reduction");
    
    each2.acc=acc;
    each2.result=result;
    for ( i = 0; i < N_CHUNKS; i += 1) {
        acemesh_push_rlist(1, &acc[i],NORMAL);
    }    
    each2.length=N_CHUNKS;
//    printf("acc addr=%p",acc);
//#ifdef MASTER
    acemesh_task_map_master();
    acemesh_task_generator((TASK_FUNCPTR)reduction_core,(void*)(&each2), sizeof(targs2));
//#else
//    acemesh_task_generator((TASK_FUNCPTR)slave_reduction_core,(void*)(&each2), sizeof(targs2));
//#endif
    acemesh_end_split_task();
    acemesh_spawn_and_wait(2);
    //#pragma oss taskwait
    return *result;
}
